package org.zachary.imitation.raft.util.timer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zachary.imitation.raft.NodeImpl;
import org.zachary.imitation.raft.option.NodeOptions;
import org.zachary.imitation.raft.util.Describer;
import org.zachary.utils.concurrent.NamedThreadFactory;
import org.zachary.utils.lang.Requires;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 超时选举定时器，内部持有一个时间轮工具，start 方法为其入口
 */
public abstract class Repeated implements Describer {
	public static final Logger logger = LoggerFactory.getLogger(Repeated.class);

	/** 定时任务管理器，这里的实现为基于时间轮的 {@link HashedWheelTimer}，通过沟通函数传入 */
	private final Timer timer;
	/** 超时时间的抽象，实际实现为 HashedWheelTimeout */
	private Timeout timeout;

	// region 定时器的若干种状态
	/** 定时器是否已经停止工作 */
	private boolean stopped;
	/** 定时器是否正在运行 */
	private volatile boolean running;
	/** 定时器是否被销毁 */
	private volatile boolean destroyed;
	/** 定时器是否正在调用超时选举方法 */
	private volatile boolean invoking;
	/** 为了防止出现并发问题，定义一个锁对象 */
	private final Lock lock = new ReentrantLock();
	// endregion

	/** 超时选举时间 */
	private volatile int timeoutMs;
	private final String name;

	public int getTimeoutMs() {
		return this.timeoutMs;
	}

	public Repeated(final String name, final int timeoutMs) {
		this(name, timeoutMs, new HashedWheelTimer(new NamedThreadFactory(name, true), 1, TimeUnit.MILLISECONDS, 2048));
	}

	/** 该构造方法会在 {@link NodeImpl#init(NodeOptions)} 方法中被调用以创建 RepeatedTimer 对象 */
	public Repeated(final String name, final int timeoutMs, final Timer timer) {
		super();
		this.name = name;
		this.timeoutMs = timeoutMs;
		this.stopped = true;
		this.timer = Requires.requireNonNull(timer, "timer");
	}


	/** 超时时间到了后要执行的业务逻辑。比如在 {@link NodeImpl#init(NodeOptions)} 中调用 handleElectionTimeout 方法，超时执行预投票 */
	protected abstract void onTrigger();

	/** 在给定的超时时间上进行一些调整得到一个新的超时时间，子类可以覆写这个方法在给定的超时时间上增加一个随机的数字以实现随机的超时时间 */
	protected int adjustTimeout(final int timeoutMs) {
		return timeoutMs;
	}

	/** 类对象的核心方法，时间轮线程中执行的实际上就是该类对象中的 run 方法 */
	public void run() {
		this.invoking = true;
		try {
			// 调用下面这个方法后，会在该方法内部进一步调用 handleElectionTimeout 方法方法进行预投票活动
			onTrigger();
		} catch (final Throwable t) {
			logger.error("Run timer failed.", t);
		}
		boolean invokeDestroyed = false;
		this.lock.lock();
		try {
			this.invoking = false;
			// 判断该定时器有没有终止
			if (this.stopped) {
				this.running = false;
				invokeDestroyed = this.destroyed;
			} else {
				// 走到这里说明定时器还在正常活动
				this.timeout = null;
				// 再次调用 schedule 方法，其实就是向时间轮提交了下一个选举超时任务，到时间执行即可
				schedule();
			}
		} finally {
			this.lock.unlock();
		}
		if (invokeDestroyed) {
			onDestroy();
		}
	}

	public void runOnceNow() {
		this.lock.lock();
		try {
			if (this.timeout != null && this.timeout.cancel()) {
				this.timeout = null;
				run();
			}
		} finally {
			this.lock.unlock();
		}
	}


	protected void onDestroy() {
		logger.info("Destroy timer: {}.", this);
	}

	/** 启动定时器 */
	public void start() {
		this.lock.lock();
		try {
			if (this.destroyed) {
				return;
			}
			if (!this.stopped) {
				return;
			}
			this.stopped = false;
			if (this.running) {
				return;
			}
			this.running = true;
			// 开始调度定时器管理的定时任务
			schedule();
		} finally {
			this.lock.unlock();
		}
	}

	/** 重新启动该定时器 */
	public void restart() {
		this.lock.lock();
		try {
			if (this.destroyed) {
				return;
			}
			this.stopped = false;
			this.running = true;
			schedule();
		} finally {
			this.lock.unlock();
		}
	}

	/** 启动该类代表的定时器之后，就会在下面这个方法中创建一个 TimerTask 任务，并且把这个任务提交到时间轮中，设置好多少时间之后执行该定时任务 */
	private void schedule() {
		// 取消上一个任务
		if (this.timeout != null) {
			this.timeout.cancel();
		}
		final TimerTask timerTask = (Timeout timeout) -> {
			try {
				Repeated.this.run();
			} catch (final Throwable t) {
				logger.error("Run timer task failed, taskName={}.", Repeated.this.name, t);
			}
		};
		// 创建一个新的定时任务
		this.timeout = this.timer.newTimeout(timerTask, adjustTimeout(this.timeoutMs), TimeUnit.MILLISECONDS);
	}

	/** 重置超时选举时间，然后调度超时选举任务 */
	public void reset(final int timeoutMs) {
		this.lock.lock();
		this.timeoutMs = timeoutMs;
		try {
			if (this.stopped) {
				return;
			}
			if (this.running) {
				schedule();
			}
		} finally {
			this.lock.unlock();
		}
	}

	public void reset() {
		this.lock.lock();
		try {
			reset(this.timeoutMs);
		} finally {
			this.lock.unlock();
		}
	}


	public void destroy() {
		boolean invokeDestroyed = false;
		this.lock.lock();
		try {
			if (this.destroyed) {
				return;
			}
			this.destroyed = true;
			if (!this.running) {
				invokeDestroyed = true;
			}
			if (this.stopped) {
				return;
			}
			this.stopped = true;
			if (this.timeout != null) {
				if (this.timeout.cancel()) {
					invokeDestroyed = true;
					this.running = false;
				}
				this.timeout = null;
			}
		} finally {
			this.lock.unlock();
			this.timer.stop();
			if (invokeDestroyed) {
				onDestroy();
			}
		}
	}

	public void stop() {
		this.lock.lock();
		try {
			if (this.stopped) {
				return;
			}
			this.stopped = true;
			if (this.timeout != null) {
				this.timeout.cancel();
				this.running = false;
				this.timeout = null;
			}
		} finally {
			this.lock.unlock();
		}
	}

	@Override
	public void describe(final Printer out) {
		final String _describeString;
		this.lock.lock();
		try {
			_describeString = toString();
		} finally {
			this.lock.unlock();
		}
		out.print("  ").println(_describeString);
	}

	@Override
	public String toString() {
		return "RepeatedTimer{" + "timeout=" + this.timeout + ", stopped=" + this.stopped + ", running=" + this.running
				+ ", destroyed=" + this.destroyed + ", invoking=" + this.invoking + ", timeoutMs=" + this.timeoutMs
				+ ", name='" + this.name + '\'' + '}';
	}
}
